﻿
using BodaNetCommunications.Nodes;
using BodaNetCommunications.Utilities.ComTypes;
using BodaNetCommunications.Utilities.Results;

using Newtonsoft.Json.Linq;

using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;

namespace BodaNetCommunications.OpcComs
{
    public class OpcUaService : IOpcUaService
    {
        private Session _session;

        public bool IsConnected => _session != null && _session.Connected;

        public OpcUaService()
        {
        }

        /// <summary>
        /// OpcUa的连接:
        ///     注意超时时间，超时没有数据读取，Session会自动退出
        /// </summary>
        /// <param name="discoveryUrl">opc.tcp://10.0.12.35:50000</param>
        /// <param name="sessionName"></param>
        /// <param name="sessionTimeout">这个超时时间，如果在这段时间没有数据读取，则Session会自动退出</param>
        /// <returns></returns>
        public BodaResult AnonymousConnect(string discoveryUrl, string sessionName, uint sessionTimeout = 3000)
        {
            BodaResult bodaResult = new BodaResult();
            try
            {
                // ApplicationConfiguration configuration, // 客户端应用配置信息
                // ConfiguredEndpoint endpoint,
                // bool updateBeforeConnect,
                // string sessionName,
                // uint sessionTimeout,
                // IUserIdentity identity,
                // IList<string> preferredLocales

                ApplicationConfiguration app = new ApplicationConfiguration();
                // 需要初始化，否则可能会报错
                app.ClientConfiguration = new ClientConfiguration()
                {
                    DefaultSessionTimeout = -1,
                    MinSubscriptionLifetime = -1
                };

                EndpointDescription desp = CoreClientUtils.SelectEndpoint(discoveryUrl: discoveryUrl, useSecurity: false); // 这里需要进行TCP的三次握手，建立连接
                                                                                                                           //var config = new EndpointConfiguration();
                desp.EndpointUrl = discoveryUrl;

                ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, desp);
                var updateBeforeConnect = false; // 更新服务节点的描述

                // 用户密码
                IUserIdentity identity = new UserIdentity();
                IList<string> preferredLocales = new List<string>();

                // 获取一个操作对象
                var seesion = Session.Create(app, endpoint, updateBeforeConnect, sessionName, sessionTimeout == 0 ? 3000 : sessionTimeout, identity, preferredLocales);

                _session = seesion.GetAwaiter().GetResult();
                if (_session != null && _session.Connected)
                {
                    bodaResult.IsSuccess = true;
                    bodaResult.Message = "Connected OK";
                }
                else
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "Connected failed";
                }

            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
            }
            return bodaResult;
        }


        public Task<Session> UserNameConnectAsync(string useName, string pwd, string discoveryUrl, string sessionName, uint sessionTimeout)
        {
            // ApplicationConfiguration configuration, // 客户端应用配置信息
            // ConfiguredEndpoint endpoint,
            // bool updateBeforeConnect,
            // string sessionName,
            // uint sessionTimeout,
            // IUserIdentity identity,
            // IList<string> preferredLocales

            CertificateValidator validator = new CertificateValidator();
            // 针对认证过程中的认证处理
            validator.CertificateValidation += (se, ev) =>
            {
                // 可以通过code判断是否，对不信任的认证的处理
                var statusCode = ev.Error.StatusCode;
                if (statusCode == StatusCodes.BadCertificateUntrusted)
                {
                    ev.Accept = true; // 放行，认为是正常操作
                }

                // 如果有其他的认证结果，继续单独处理
            };
            SecurityConfiguration security = new SecurityConfiguration()
            {
                RejectSHA1SignedCertificates = false
            };
            validator.Update(security);
            ApplicationConfiguration app = new ApplicationConfiguration() // 客户端连接配置
            {
                CertificateValidator = validator
            };
            // 需要初始化，否则可能会报错
            app.ClientConfiguration = new ClientConfiguration()
            {
                DefaultSessionTimeout = -1,
                MinSubscriptionLifetime = -1,
            };

            // 连接配置
            // 泉为：opc.tcp://192.168.13.22:50000
            // 本地：opc.tcp://127.0.0.1:49320"
            EndpointDescription desp = CoreClientUtils.SelectEndpoint(discoveryUrl: discoveryUrl, useSecurity: false); // 这里需要进行TCP的三次握手，建立连接
            //var config = new EndpointConfiguration();
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, desp);
            var updateBeforeConnect = false; // 更新服务节点的描述
            IUserIdentity identity = new UserIdentity(username: useName, password: pwd);
            IList<string> preferredLocales = new List<string>();

            // 获取一个操作对象
            return Session.Create(app, endpoint, updateBeforeConnect, sessionName, sessionTimeout, identity, preferredLocales);

        }

        public async Task<Session> CertificateConnectAsync(string useName, string pwd, string appName, string appHost, string discoveryUrl, string sessionName, uint sessionTimeout)
        {
            #region 客户端应用连接配置信息
            ApplicationConfiguration configuration = new ApplicationConfiguration();

            #region 配置configuration对象的 ClientConfiguration属性
            // 当创建连接对象时，必须给configuration的ClientConfiguration属性进行初始化
            configuration.ClientConfiguration = new ClientConfiguration();
            #endregion

            #region 配置configuration对象的 CertificateValidator属性
            CertificateValidator validator = new CertificateValidator();
            // 针对登录过程中的认证处理
            validator.CertificateValidation += (se, ev) =>
            {
                // 把不信任认证拦截
                if (ev.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted)
                    ev.Accept = true;// 放行，认为是正常操作

                // 如果其他的认证结果，继续单独处理
            };

            configuration.CertificateValidator = validator;
            #endregion

            #endregion

            #region 认证连接配置
            // 客户端名称 
            configuration.ApplicationName = appName;
            // 认证URI
            configuration.ApplicationUri = $"urn:{appHost}:{appName}";

            // 安全配置
            configuration.SecurityConfiguration = new SecurityConfiguration()
            {
                RejectSHA1SignedCertificates = false,
                MinimumCertificateKeySize = 1024,

                ApplicationCertificate = new CertificateIdentifier
                {
                    StoreType = CertificateStoreType.X509Store,
                    StorePath = "CurrentUser\\" + appName,
                    SubjectName = appName
                }
            };
            // 客户端配置信息验证
            await configuration.Validate(ApplicationType.Client);

            ApplicationInstance applicationInstance = new ApplicationInstance();
            applicationInstance.ApplicationConfiguration = configuration;
            // 生成证书并提交到服务器进行验证，
            bool state = await applicationInstance.CheckApplicationInstanceCertificate(false, 1024);
            #endregion

            #region 连接地址
            // 这里需要进行TCP的三次握手
            EndpointDescription description = CoreClientUtils.SelectEndpoint(
                discoveryUrl: discoveryUrl,
                useSecurity: false
            );
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, description);
            #endregion

            //  创建一个操作对象
            return await Session.Create(
                configuration,
                endpoint,
                false,
                useName,
                sessionTimeout,
                new UserIdentity(useName, pwd),
                new string[] { }
                );
        }

        public BodaResult UARead(List<RequestNode> nodes, Action? inputCallback)
        {
            BodaResult bodaResult = new BodaResult();
            try
            {
                if (_session == null || !_session.Connected)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUa Session is not created";
                    return bodaResult;
                }
                if (nodes == null || nodes.Count == 0)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUs nodes is zero";
                    return bodaResult;
                }

                ReadValueIdCollection readValueIds = new ReadValueIdCollection();
                foreach (var node in nodes)
                {
                    node.ReadVarValue = string.Empty;
                    if (string.IsNullOrEmpty(node.NodeAddress)) continue;
                    //string input = "ns=2;s=Object.HMI.PB1GlassplcGlasslD";

                    // 匹配的是包含字母、数字、下划线、等号、分号或句点的任意字符序列
                    string input = node.NodeAddress.Trim();
                    //string pattern = @"[\w=;.]+";
                    //string result = "";

                    //foreach (Match match in Regex.Matches(input, pattern))
                    //{
                    //    result += match.Value;
                    //}

                    readValueIds.Add(new ReadValueId
                    {
                        NodeId = input,
                        AttributeId = Attributes.Value
                    });


                    //#if DEBUG

                    //                    readValueIds.Add(new ReadValueId
                    //                    {
                    //                        NodeId = "ns=2;s=Tag.Statistics.PLC.NGString",
                    //                        AttributeId = Attributes.Value
                    //                    });


                    //                    readValueIds.Add(new ReadValueId
                    //                    {
                    //                        NodeId = "ns=2;s=Tag.MES.HeFeiXieXin.MR815",
                    //                        AttributeId = Attributes.Value
                    //                    });


                    //                    readValueIds.Add(new ReadValueId
                    //                    {
                    //                        NodeId = "ns=2;s=Tag.MES.HeFeiXieXin.ZF71730",
                    //                        AttributeId = Attributes.Value
                    //                    });
                    //#endif
                }

                var header = new RequestHeader();
                //header.TimeoutHint = 100;

                var readRes = _session.Read(
                          // 请求参数
                          header,
                          // 数据缓存毫秒， 0既是当前的数据
                          0,
                          // 返回结果的时间戳， 返回服务端和客户端的时间戳
                          TimestampsToReturn.Both,
                          readValueIds,
                          out var resultVals,
                          out var diagnosticInfos);

                if (readRes == null || !StatusCode.IsGood(readRes.ServiceResult))
                {
                    inputCallback?.Invoke();

                    bodaResult.IsSuccess = true;
                    bodaResult.Message = $"OpcUa[{_session.SessionName}] => Read value result is bad";
                    return bodaResult;
                }

                // 返回值的顺序和输入的Id顺序一致
                for (int i = 0; i < nodes.Count; i++)
                {
                    nodes[i].ReadVarValue = resultVals[i].Value;

                    BuiltInType dataType = resultVals[i].WrappedValue.TypeInfo.BuiltInType;
                    nodes[i].ReadVarType = TypeConverter(dataType);
                }

                inputCallback?.Invoke();
                bodaResult.IsSuccess = true;
                bodaResult.Message = "Read Ok";
                return bodaResult;
            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
                return bodaResult;
            }

        }


        private DeviceValueTypeEnum TypeConverter(BuiltInType builtInType)
        {
            DeviceValueTypeEnum deviceValueTypeEnum = DeviceValueTypeEnum.None;

            switch (builtInType)
            {
                case BuiltInType.Null:
                    break;
                case BuiltInType.Boolean:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@bool;
                    break;
                case BuiltInType.SByte:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@sbyte;
                    break;
                case BuiltInType.Byte:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@byte;
                    break;
                case BuiltInType.Int16:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@short;
                    break;
                case BuiltInType.UInt16:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@ushort;
                    break;
                case BuiltInType.Int32:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@int;
                    break;
                case BuiltInType.UInt32:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@uint;
                    break;
                case BuiltInType.Int64:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@long;
                    break;
                case BuiltInType.UInt64:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@ulong;
                    break;
                case BuiltInType.Float:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@float;
                    break;
                case BuiltInType.Double:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@double;
                    break;
                case BuiltInType.String:
                    deviceValueTypeEnum = DeviceValueTypeEnum.@string;
                    break;
                case BuiltInType.DateTime:
                    deviceValueTypeEnum = DeviceValueTypeEnum.datetime;
                    break;
                case BuiltInType.Guid:
                    break;
                case BuiltInType.ByteString:
                    break;
                case BuiltInType.XmlElement:
                    break;
                case BuiltInType.NodeId:
                    break;
                case BuiltInType.ExpandedNodeId:
                    break;
                case BuiltInType.StatusCode:
                    break;
                case BuiltInType.QualifiedName:
                    break;
                case BuiltInType.LocalizedText:
                    break;
                case BuiltInType.ExtensionObject:
                    break;
                case BuiltInType.DataValue:
                    break;
                case BuiltInType.Variant:
                    break;
                case BuiltInType.DiagnosticInfo:
                    break;
                case BuiltInType.Number:
                    break;
                case BuiltInType.Integer:
                    break;
                case BuiltInType.UInteger:
                    break;
                case BuiltInType.Enumeration:
                    break;
            }

            return deviceValueTypeEnum;
        }

        public BodaResult UAReadAsync(List<RequestNode> nodes, Action? inputCallback)
        {
            BodaResult bodaResult = new BodaResult();
            try
            {
                if (_session == null || !_session.Connected)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUa Session is not created";
                    return bodaResult;
                }

                if (nodes == null || nodes.Count == 0)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUs nodes is zero";
                    return bodaResult;
                }

                ReadValueIdCollection readValueIds = new ReadValueIdCollection();
                foreach (var node in nodes)
                {
                    readValueIds.Add(new ReadValueId
                    {
                        NodeId = node.NodeAddress,
                        AttributeId = Attributes.Value
                    });
                }
                //CancellationToken cancellationToken = new CancellationToken();
                //var result = await session.ReadAsync(null, 0, TimestampsToReturn.Both, readValueIds, cancellationToken);

                var readRes = _session.BeginRead(
                      new RequestHeader(),
                      0,
                      TimestampsToReturn.Both,
                      readValueIds,
                      callback: result =>
                      {
                          _session.EndRead(
                              result,
                              out DataValueCollection results,
                              out DiagnosticInfoCollection diagnosticInfos);

                          for (int i = 0; i < nodes.Count; i++)
                          {
                              nodes[i].ReadVarValue = results[i].Value;
                              //_logger?.LogInformation($"sesson[{session.SessionName}] =>Node[{nodes[i].NodeAddress}]  read value is {results[i].Value}");
                          }

                          inputCallback?.Invoke();
                      },
                      _session
                      );

                if (readRes == null)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "Read result is null";
                    return bodaResult;
                }

                //_logger?.LogInformation($"sesson[{session.SessionName}] Reading data waits begin, IsCompleted:{readRes.IsCompleted}, CompletedSynchronously:{readRes.CompletedSynchronously},AsyncState:{readRes.AsyncState.ToString()}");
                readRes.AsyncWaitHandle.WaitOne();

                //_logger?.LogInformation($"sesson[{session.SessionName}] Reading data waits end, IsCompleted:{readRes.IsCompleted}, CompletedSynchronously:{readRes.CompletedSynchronously},AsyncState:{readRes.AsyncState.ToString()}");
                if (readRes.IsCompleted)
                {
                    //_logger?.LogInformation($"sesson[{session.SessionName}] Reading data completed, IsCompleted:{readRes.IsCompleted}, CompletedSynchronously:{readRes.CompletedSynchronously},AsyncState:{readRes.AsyncState.ToString()}");
                    bodaResult.IsSuccess = true;
                    bodaResult.Message = "Read Ok";
                }
                else
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "Read is not completed";
                }
                return bodaResult;
            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
                return bodaResult;
            }

        }

        public BodaResult<ReferenceDescriptionCollection> BrowseNodes(string nodeId)
        {
            BodaResult<ReferenceDescriptionCollection> bodaResult = new BodaResult<ReferenceDescriptionCollection>();
            if (_session == null || !_session.Connected)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = "OpcUa Session is not created";
                return bodaResult;
            }

            try
            {
                // 两种处理方式
                Browser browser = new Browser(_session);
                var collection = browser.Browse(new NodeId(nodeId)); // 根节点
                bodaResult.IsSuccess = true;
                bodaResult.Content = collection;
                return bodaResult;
            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
                return bodaResult;
            }
        }

        public void DisConnect()
        {
            _session?.Close();
            _session?.Dispose();
            _session = null;
        }

        public BodaResult UAWrite(List<RequestNode> nodes, Action? inputCallback)
        {
            BodaResult bodaResult = new BodaResult();
            try
            {
                if (_session == null || !_session.Connected)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUa Session is not created";
                    return bodaResult;
                }
                if (nodes == null || nodes.Count == 0)
                {
                    bodaResult.IsSuccess = false;
                    bodaResult.Message = "OpcUs nodes is zero";
                    return bodaResult;
                }

                WriteValueCollection writeValueIds = new WriteValueCollection();
                foreach (var node in nodes)
                {
                    node.ReadVarValue = string.Empty;
                    if (string.IsNullOrEmpty(node.NodeAddress)) continue;

                    // 匹配的是包含字母、数字、下划线、等号、分号或句点的任意字符序列
                    string input = node.NodeAddress.Trim();
                    var nodeOpc = new WriteValue
                    {
                        NodeId = input,
                        AttributeId = Attributes.Value,
                    };
                    nodeOpc.Value.Value = node.WriteVarValue;

                    writeValueIds.Add(nodeOpc);
                }

                var header = new RequestHeader();
                var readRes = _session.Write(
                          // 请求参数
                          header,
                          // 数据缓存毫秒， 0既是当前的数据
                          writeValueIds,
                           out StatusCodeCollection results,
                            out DiagnosticInfoCollection diagnosticInfos);

                if (readRes == null || !StatusCode.IsGood(readRes.ServiceResult))
                {
                    inputCallback?.Invoke();

                    bodaResult.IsSuccess = true;
                    bodaResult.Message = $"OpcUa[{_session.SessionName}] => Write value result is bad";
                    return bodaResult;
                }

                // 返回值的顺序和输入的Id顺序一致
                for (int i = 0; i < writeValueIds.Count; i++)
                {
                    bodaResult.IsSuccess &= StatusCode.IsGood(results[i]);
                }
                inputCallback?.Invoke();
                bodaResult.Message = "Write Ok";
                return bodaResult;
            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
                return bodaResult;
            }

        }
    }
}